Luke Eades's Blog

Simple CSS underline animations


Hyperlinks are something that define the web, and something that has defined hyperlinks is the underline. It’s a convention that we can’t seem to escape no matter what, that if something is underlined it is assumed to be a link. I could argue that underlines are overused and we should find better alternatives but it’s also a more than fair argument that to make a link the most familiar, an underline is the way to go.

Underlines on the web are still widely used and there is better support than ever for more advanced effects.

Often, to create fancy underline effects, people use ::before and ::after psuedo elements. You can do some cool stuff with these, but they should be limited to headings and small links because they aren’t capable of doing line-wrapping. Most of the time similar effects can be achieved with native underlines or background images.

The go-to for underlines should be “plain” css underlines. These support line-wrapping and descender skipping, unlike most other techniques. Descender skipping browser support is around 94% as of writing, with some of safari’s features being locked behind -webkit prefixing and is enabled by default on browsers that support it.

Relevant CSS propeties

The most important property for underlines is text-decoration, which is actually shorthand syntax for a few other properties. Once the individual properties are understood then it may be more efficient to use the shorthand, which is nothing more than sticking all the values together in the right order.

These properties are:

  • text-decoration-line
  • text-decoration-style
  • text-decoration-color
  • text-decoration-thickness

Safari doesn’t support the shorthand style without -webkit prefixing, and even then the text-decoration-thickness property is not included, so it’s safer to declare that one separately.

Another property that is quite useful specifically for styling underlines is the text-underline-offset property.

Let’s go through them one by one.

The text-decoration-line property

All this property does is decide what type of line will be drawn. For our case that will be an underline so the value is underline. It can also be set to things like overline and line-through.

The text-decoration-style property

This property determines what the line will look like. The options are solid, double, dotted, dashed, and wavy. They look just like they sound.


.solid {
  text-decoration-style: solid;
}
.double {
  text-decoration-style: double;
}
.dotted {
  text-decoration-style: dotted;
}
.dashed {
  text-decoration-style: dashed;
}
.wavy {
  text-decoration-style: wavy;
}

The text-decoration-color property

This determines the, well, color of the underline. It’s quite useful for animating the transparency because there isn’t a seperate opacity property.

The initial value is currentColor so it matchs the text it’s underlining by default.


.solid {
  text-decoration-color: rebeccapurple;
}
.animating {
  animation: 3s alternate-colors infinite alternate;
}
.transparent {
  animation: 3s alternate-transparency infinite alternate;
}
@keyframes alternate-colors {
  from {
    text-decoration-color: purple
  }
  to {
    text-decoration-color: green;
  }
}
@keyframes alternate-transparency {
  from {
    text-decoration-color: rgb(0 0 0 / .0);
  }
  to {
    text-decoration-color: rgb(0 0 0 / 1);
  }
}

The text-decoration-thickness property

This property changes the thickness of the line. I think this improves the look over the default underlines quite a bit if you just increase it by a bit. It’s essential to use relative units like ems or %s so that the lines scale according to the font size.


.small {
  text-decoration-thickness: .1em;
}
.medium {
  text-decoration-thickness: .2em;
}
.large {
  text-decoration-thickness: .3em;
}

The text-underline-offset property

This property is specific to the underline type of text decoration. It determines the distance the underline is from the baseline. Make sure to use relative units with this one too.


.none {
  text-underline-offset: 0;
}
.some {
  text-underline-offset: .2em;
}
.animating {
  animation: 2s in-out infinite alternate;
}
@keyframes in-out {
  from {
    text-underline-offset: .1em;
  }
  to {
    text-underline-offset: .4em;
  }
}

Examples

When you put them all together and add some transitions you can make some pretty decent animations very simply, which 99% of the time is all you need.

The ones below apply on hover and focus.


.example-1 {
  text-underline-offset: .4em;
  text-decoration-color: transparent;
  transition: var(--duration) text-decoration-color,
    var(--duration) text-underline-offset;
  &:hover, &:focus {
    text-underline-offset: .1em;
    text-decoration-color: currentColor;
  }
}
.example-2 {
  text-underline-offset: .1em;
  transition: var(--duration) text-underline-offset;
  &:hover, &:focus {
    text-underline-offset: .4em;
  }
}
.example-3 {
  text-underline-offset: .1em;
  transition: var(--duration) color;
  &:hover, &:focus {
    color: teal;
  }
}
a {
  --duration: 200ms;
}

If more complicated animations are needed I recommend using background images, which I may write about later.

Resources

https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration https://www.w3.org/TR/css-text-decor-3/#line-decoration